package view;

import engine.Scheduler;
import engine.SchedulerListener;
import engine.Simulation;
import model.Model;
import javafx.application.Platform;
import javafx.beans.value.ObservableValue;
import javafx.event.ActionEvent;
import javafx.geometry.Insets;
import javafx.geometry.Orientation;
import javafx.geometry.Pos;
import javafx.geometry.Side;
import javafx.scene.chart.LineChart;
import javafx.scene.chart.NumberAxis;
import javafx.scene.chart.XYChart;
import javafx.scene.control.*;
import javafx.scene.layout.*;
import javafx.scene.paint.Color;
import javafx.scene.shape.Circle;
import javafx.scene.shape.Rectangle;
import javafx.scene.text.Font;

/**
 * Visualization of the actual model and some simulation controls.
 *
 * @author Ricardo Cruz {@literal <ricardo.pdm.cruz@gmail.com>}
 * @since  2014-11-13
 */
public class TabView extends Tab implements SimulationViewListener, SchedulerListener
{
	private SimulationViewZoomin zoomin;
	private SimulationViewZoomout zoomout;
	private Timeseries plot;
	private double update_time = (1./24)/60;
	private Label time;
	private ZoomSlider zoomSlider;
	private ToggleButton go;

	public TabView(Window window) {
		super("View");
		setClosable(false);

		plot = new Timeseries();
		plot.setPrefSize(0, 0);
		plot.setMinSize(0, 0);
		zoomin = new SimulationViewZoomin(this);
		zoomin.setPrefSize(400, 300);
		zoomin.setMinSize(0, 0);
		zoomout = new SimulationViewZoomout(this, zoomin);

		//** buttons and velocity slider

		go = new ToggleButton("_Go");
		go.setPrefWidth(80);  // a little bigger
		//go.setDefaultButton(true);
		go.setOnAction((ActionEvent ev) -> {
			if(go.isSelected())
				start();
			else
				pause();
		});
		Button reset = new Button("_Reset");
		reset.setOnAction((ActionEvent ev) -> {
			reset();
		});

		VelSlider velSlider = new VelSlider();
		Region spacer = new Region();
		time = new Label(formatTime(0));
		HBox buttonsBox = new HBox(4);
		buttonsBox.getChildren().addAll(go, reset, spacer, velSlider,
			new Label("Time:"), time);
		buttonsBox.setAlignment(Pos.CENTER);
		HBox.setHgrow(spacer, Priority.ALWAYS);

		//** simulation box

		VBox layersBox = new VBox();
		CheckBox layer;
		layer = new CheckBox();
		layer.setDisable(true);
		layer.setSelected(true);
		layer.setGraphic(new Circle(4, Model.getCellColor(0)));
		layersBox.getChildren().add(layer);
		layer = new CheckBox();
		layer.setDisable(true);
		layer.setSelected(true);
		layer.setGraphic(new Rectangle(8, 8, Color.YELLOW));
		layersBox.getChildren().add(layer);
		zoomSlider = new ZoomSlider();

		BorderPane zoomPane = new BorderPane();
		zoomPane.setTop(layersBox);
		zoomPane.setBottom(zoomSlider);

		HBox zoominBox = new HBox();
		zoominBox.getChildren().addAll(zoomin, zoomPane);
		HBox.setHgrow(zoomin, Priority.ALWAYS);

		HBox overviewBox = new HBox();
		overviewBox.getChildren().addAll(zoomout, plot);
		HBox.setHgrow(plot, Priority.ALWAYS);

		//** window panel

		VBox vbox = new VBox(6);
		vbox.getChildren().addAll(buttonsBox, zoominBox, overviewBox);
		VBox.setVgrow(zoominBox, Priority.ALWAYS);
		vbox.setPadding(new Insets(6));
		setContent(vbox);

		Scheduler.post(update_time, this, null);
	}

	public void start() {
		Platform.runLater(new SchedulerProcess());
	}

	public void pause() {
	}

	public void reset() {
		go.setSelected(false);
		Simulation.create(Model.getCurrentModel());
		time.setText(formatTime(0));
		zoomin.repaint();
		zoomout.repaint();
		plot.reset();
		Scheduler.post(update_time, this, null);
	}

	private class SchedulerProcess implements Runnable {
		public void run() {
			Scheduler.process();
			if(go.isSelected())
				Platform.runLater(this);
		}
	}

	public void event(Object event) {
		Simulation s = Simulation.get();
		time.setText(formatTime(Scheduler.clock()));
		zoomin.repaint();
		zoomout.repaint();
		plot.add(Scheduler.clock(), s.getAgentsLayer().getCellCount());

		Scheduler.post(update_time, this, null);
	}

	public void moveSimulation(double x, double y) {
		zoomin.repaint();
		zoomout.repaint();
	}

	public void zoomSimulation(double zoom) {
		zoomin.repaint();
		zoomout.repaint();
		zoomSlider.setValue(zoom);
	}

	private String formatTime(double clock) {
		int days = (int)clock;
		int hours = (int)((clock-days)*24);
		float mins = (float)(((clock-days)*24-hours)*60);
		return String.format("%dd %02dh %04.1fm", days, hours, mins);
	}

	private static class Timeseries extends LineChart<Number, Number> {
		public Timeseries() {
			super(createAxis(), createAxis());
			setLegendSide(Side.RIGHT);
			setLegendVisible(true);
			setCreateSymbols(false);
			setAnimated(false);
			reset();
		}

		public void add(double time, int ... count) {
			int i = 0;
			for(Series<Number, Number> series: getData()) {
				if(series.getData().size() > 1000)  // for efficiency
					series.getData().remove(0, 500);
				series.getData().add(new XYChart.Data<Number,Number>(
					time, count[i++]));
			}
		}

		public void reset() {
			final String[] legend = Model.getModelLegend(Model.getCurrentModel());
			getData().clear();
			for(int i = 0; i < legend.length; i++) {
				Series<Number,Number> s = new Series<Number,Number>();
				s.setName(legend[i]);
				getData().add(s);
			}
		}

		private static NumberAxis createAxis() {
			NumberAxis axis = new NumberAxis();
			axis.setTickLabelsVisible(true);
			axis.setAnimated(false);
			return axis;
		}
	}

	private class ZoomSlider extends VBox {
		private Slider slider;

		public ZoomSlider() {
			super(2);
			Tooltip tooltip = new Tooltip(
				"note:\n\u2022 1 cell = 4\u00B5m\n\u2022 1 molecule = 20\u00B5m");
			Label label = new Label("1\u00B5m=\n0.25px");
			label.setTooltip(tooltip);
			label.setFont(Font.font(8));
			label.setPrefWidth(28);
			label.setAlignment(Pos.CENTER_LEFT);
			slider = new Slider(1, 14, 1);
			slider.setTooltip(tooltip);
			slider.setOrientation(Orientation.VERTICAL);
			slider.setMinorTickCount(0);
			slider.setMajorTickUnit(1);
			slider.snapToTicksProperty().set(true);
			slider.valueProperty().addListener((
				/* zoomSlider is in pixel per cell units (i.e. how many pixels for
				 * one cell. Each cell is 4x4 um, so we must divide these values by
				 * 4 when passing to SimulationView.
				 */
				ObservableValue<? extends Number> ov, Number old_val, Number value) -> {
					int zoom = value.intValue();
					label.setText("1\u00B5m=\n" + String.valueOf(zoom/4.) + "px");
					zoomin.setZoom(zoom/4.);
					zoomSimulation(zoom/4.);
			});
			getChildren().addAll(slider, label);
			setAlignment(Pos.CENTER);
		}
		public void setValue(double zoom) {
			slider.setValue(zoom*4);
		}
	}

	private class VelSlider extends HBox {
		public VelSlider() {
			final double vel[] = new double[] { (1./24)/60, 1./48, 1./24, 1 };
			final String vell[] = new String[] { "1m", "30m", "1h", "1d" };

			Font smallFont = Font.font(8);
			Label velLabel = new Label("\u0394t=");
			velLabel.setFont(smallFont);
			velLabel.setAlignment(Pos.BOTTOM_RIGHT);
			velLabel.setMaxHeight(Double.MAX_VALUE);

			HBox velTicks = new HBox();
			for(int i = 0; i < vell.length; i++) {
				Label t = new Label(vell[i]);
				t.setFont(smallFont);
				if(i < vell.length-1) {
					HBox.setHgrow(t, Priority.ALWAYS);
					t.setMaxWidth(Double.MAX_VALUE);
				}
				velTicks.getChildren().add(t);
			}
			Slider velSlider = new Slider(0,vel.length-1, 0);
			velSlider.setMajorTickUnit(1);
			velSlider.setMinorTickCount(0);
			velSlider.setBlockIncrement(1);
			velSlider.setShowTickMarks(true);
			velSlider.setSnapToTicks(true);
			velSlider.valueProperty().addListener(
				(ObservableValue<? extends Number> ov,  Number oldValue, Number value) -> {
					update_time = vel[value.intValue()];
			});
			VBox sliderBox = new VBox();
			sliderBox.getChildren().addAll(velSlider, velTicks);
			getChildren().addAll(velLabel, sliderBox);
		}
	}
}
